package org.example;

import javax.swing.*;
import java.awt.*;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
// 单例模式：确保 Utils 类只有一个实例
public class Utils implements GameStateSubject{
    static final int X=50;
    static final int Y=75;
    static final int ROW=16;
    static final int COL=16;
    static final int SIZE=50;
    static int row;//行
    static int col;//列
    static int chessType=0;//类型，0为空棋子，1为黑棋子，2为白棋子
    static int[][] chessArray=new int[ROW][COL];//各个位置棋子的类型
    static int[][] chessIndexArr=new int[256][2];//chessIndexArr[i][0]为第i个棋子的行位置，chessIndexArr[i][0]为第i个棋子的列位置
    static int chessSize=0;//棋子总个数
    static String acType;
    static int replayIndex =0;
    //单例模式：确保 Utils 类只有一个实例
    private static Utils instance;
    private List<GameStateObserver> observers = new ArrayList<>();
    private WinStrategy winStrategy = new DefaultWinStrategy();

    public Utils() {}

    public static Utils getInstance() {
        if (instance == null) {
            instance = new Utils();
        }
        return instance;
    }

    @Override
    public void registerObserver(GameStateObserver observer) {
        observers.add(observer);
    }

    @Override
    public void removeObserver(GameStateObserver observer) {
        observers.remove(observer);
    }

    @Override
    public void notifyObservers() {
        for (GameStateObserver observer : observers) {
            observer.update();
        }
    }
    public static void drawChessPad(Graphics g){
        g.setColor(new Color(28, 147, 123));//设置实心矩形的颜色
        g.fillRect(X-SIZE/2,Y-SIZE/2,SIZE*ROW,SIZE*COL);//画出实心矩形，（X,Y）为左上角第一个横线和竖线交汇点的坐标，SIZE为棋子的直径，ROW为横线行数，COL为竖线列数
        // 因为棋子要落在交汇点上，所以在边沿上的棋子会有一半落在横线和竖线组成的范围之外
        //所以棋盘实心矩形的左上角起点为（X-SIZE/2,Y-SIZE/2），因为一行最多下COL个棋子，一列最多下ROW个棋子
        //因此，宽度为SIZE*ROW，高度为SIZE*COL
        g.setColor(Color.BLACK);//设置横线和竖线的颜色
        for(int i=0;i<ROW;i++){//ROW、COL都为16，因此画横线和竖线可在一个循环里
            g.drawLine(X,Y+i*SIZE,X+(COL-1)*SIZE,Y+i*SIZE);//画横线
            g.drawLine(X+i*SIZE,Y,X+i*SIZE,Y+(ROW-1)*SIZE);//画竖线
        }
    }
    public static void drawChessIndexStr(Graphics g, int row, int col, int chessIndex) {
        //根据给的行列值，计算出交点的坐标（即棋子的中心）
        int chessX=X+col*SIZE;//X坐标为横向的，故用列来计算
        int chessY=Y+row*SIZE;//Y坐标为纵向的，故用行来计算
        g.setColor(new Color(255, 0, 0, 255));//设置字体颜色
        g.setFont(new Font("宋体", Font.BOLD, 18));//设置字体，以及大小
        int px = 5;//横坐标偏移量，为使步数在棋子的正中间
        if (chessIndex >= 10) {//因为1位数，2位数，3位数的长度不一，所以横坐标偏移量也不一
            px = chessIndex >= 100 ? 12 : 8;
        }
        g.drawString("" + chessIndex, chessX  - px, chessY  + 5);//画字符串，以（chessX  - px, chessY  + 5）为起始位置，注：起始位置为字符串的左下方
    }
    public static void modifyData(int x,int y){
        //根据坐标计算出行列数
        row=(y-Y+SIZE/2)/SIZE;//修改行，算实心矩形起始位置Y-SIZE/2到y有多少个整SIZE，即为行数的编号
        col=(x-X+SIZE/2)/SIZE;//修改列，算实心矩形起始位置X-SIZE/2到x有多少个整SIZE，即为列数的编号
        if(x<X-SIZE/2||y<Y-SIZE/2||row>=ROW||col>=COL){//若（X,Y）不在棋盘上则提示并返回
            JOptionPane.showConfirmDialog(null,"请将旗子落在棋盘内！");
            return;
        }
        if(chessArray[row][col]!=0){//如果chessArray[row][col]!=0则在此处已有棋子，不可再放棋子
            JOptionPane.showConfirmDialog(null,"此处已有棋子！");
            return;
        }
        chessType=chessSize%2+1;//棋子类型为已下棋子数%2+1
        //（即已下棋子个数为偶数，则该下黑棋，已下棋子个数为奇数，则该下白棋）
        chessArray[row][col]=chessType;//在row行col列存储棋子类型
        //存储棋子顺序（为第几个棋子）
        chessIndexArr[chessSize][0]=row;
        chessIndexArr[chessSize][1]=col;
        chessSize++;
    }
    public static void drawChess(Graphics g,int chessIndex){
        //chessIndex为将下的棋为第几步
        //计算出所画棋子的外接正方形的左上角的坐标
        //（X+col*SIZE,Y+row*SIZE）为交点坐标，横纵坐标需向左上偏移（SIZE/2,SIZE/2）
        int chessX=X+col*SIZE-SIZE/2;
        int chessY=Y+row*SIZE-SIZE/2;
        //设置颜色，1为黑色，2为白色
        if(chessType==1){
            g.setColor(Color.BLACK);
        } else if (chessType == 2) {
            g.setColor(Color.WHITE);
        }
        g.fillOval(chessX,chessY,SIZE,SIZE);//画圆
        drawChessIndexStr(g,row,col,chessIndex);//画第几步
    }
    public static void drawChessArray(Graphics g){
        for (int i = 0; i < chessSize; i++) {
            row=chessIndexArr[i][0];
            col=chessIndexArr[i][1];
            chessType=chessArray[row][col];
            drawChess(g,i+1);
        }
    }
    public static void reBackChess(Graphics g){
        //棋盘内无棋子可悔
        if(chessSize==0){
            JOptionPane.showConfirmDialog(null,"无棋可悔！");
            return;
        }
        chessSize--;//去除最后一枚棋子位置
        chessArray[chessIndexArr[chessSize][0]][chessIndexArr[chessSize][1]]=0;//修改最后一枚棋子位置上的类型
        drawChessPad(g);//画棋盘
        drawChessArray(g);//画所有棋子
    }
    public static void reGame(Graphics g){
        chessType=0;
        for (int i = 0; i < chessSize; i++) {
            chessArray[chessIndexArr[i][0]][chessIndexArr[i][1]]=0;
        }
        chessSize=0;
        drawChessPad(g);
    }
    public static int findChess(int x,int y,int dx,int dy,int type){
        //（x,y）代表最新下的棋子在chessArray中的下标，即行号和列号
        //（dx,dy）为偏移量，即向八个方向向哪个方向搜索
        //type表示最新下的棋的类型
        int cnt=0;
        for(int i=0;i<5;i++){//最多五个棋连续同类型即可判断输赢
            //向一个方向偏移一步
            x+=dx;
            y+=dy;
            //如果越界，则终止
            if(x<0||y<0||x>=COL||y>=ROW){
                break;
            }
            //如果偏移后的（x,y）位置上的棋与最新下的棋的颜色相同，则计数器+1，否则直接终止
            if(chessArray[x][y]==type){
                cnt++;
            }else {
                break;
            }
        }
        //返回计数器的值，即一个方向上连续且与最新下的棋子同类型的个数
        return cnt;
    }
    public boolean checkWin(){
        int cnt1,cnt2;
        cnt1=findChess(row,col,0,1,chessType);//搜索上方的连续与最新下的棋同类型棋子个数
        cnt2=findChess(row,col,0,-1,chessType);//搜索下方的连续与最新下的棋同类型棋子个数
        //如果所在直线上的连续同类型的个数，即相对两方向上个数之和大于4，则已赢，返回true
        if(cnt1+cnt2>=4){
            return true;
        }
        cnt1=findChess(row,col,1,0,chessType);//搜索右方的连续与最新下的棋同类型棋子个数
        cnt2=findChess(row,col,-1,0,chessType);//搜索左方的连续与最新下的棋同类型棋子个数
        if(cnt1+cnt2>=4){
            return true;
        }
        cnt1=findChess(row,col,1,1,chessType);//搜索右下方的连续与最新下的棋同类型棋子个数
        cnt2=findChess(row,col,-1,-1,chessType);//搜索左上方的连续与最新下的棋同类型棋子个数
        if(cnt1+cnt2>=4){
            return true;
        }
        cnt1=findChess(row,col,1,-1,chessType);//搜索右上方的连续与最新下的棋同类型棋子个数
        cnt2=findChess(row,col,-1,1,chessType);//搜索左下方的连续与最新下的棋同类型棋子个数
        if(cnt1+cnt2>=4){
            return true;
        }
        //如果未返回true，代表未分出胜负，返回false
        return false;
    }
    public static void saveGame(){
        JFileChooser jfc = new JFileChooser();//用于让用户在文件系统中选择文件或目录
        int saveDialog=jfc.showSaveDialog(null);
        if(saveDialog==JFileChooser.APPROVE_OPTION){//判断用户是否点击了对话框中的 “保存” 按钮
            File file=jfc.getSelectedFile();//返回用户在文件选择对话框选中的文件
            try {
                FileWriter fileWriter = new FileWriter(file);//用于把字符流数据写入到文件中
                BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);//通过在内部维护一个缓冲区来提高字符写入的效率。
                // 它可以将多个字符先暂存到缓冲区中，当缓冲区满了或者手动刷新缓冲区（调用flush方法）时，再将缓冲区中的字符一次性写入到目标输出流（比如文件对应的流）中。
                // 相比于直接使用底层的字符输出流（如FileWriter）逐个字符或逐行写入，在大量字符写入的场景下，使用BufferedWriter能够减少实际的物理写入操作次数，从而提升性能。
                bufferedWriter.write(chessSize+"#"+chessType);//写入chessSize和chessType中间用#隔开
                bufferedWriter.newLine();//换行

                //写入chessIndexArr的内容
                for (int i = 0; i < chessSize; i++) {
                    String str=chessIndexArr[i][0]+"#"+chessIndexArr[i][1];
                    bufferedWriter.write(str);
                    bufferedWriter.newLine();
                }

                //写入chessArray的内容
                for(int i=0;i<ROW;i++){
                    for (int j = 0; j < COL; j++) {
                        bufferedWriter.write(""+chessArray[i][j]);
                    }
                    bufferedWriter.newLine();
                }

                bufferedWriter.close();//关闭文件
                System.out.println("已保存"+file.getPath());
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
    public static int stringToInt(String str){
        int temp=0;
        for (int i = 0; i < str.length(); i++) {
            temp=temp*10+(int)(str.charAt(i)-'0');
        }
        return temp;
    }
    public static void readGame(){
        //打开文件
        JFileChooser jfc = new JFileChooser();
        int openDialog=jfc.showOpenDialog(null);
        if(openDialog==JFileChooser.APPROVE_OPTION){
            File file=jfc.getSelectedFile();
            try {
                FileReader fileReader = new FileReader(file);
                BufferedReader bufferedReader = new BufferedReader(fileReader);
                //读第一行
                String str= bufferedReader.readLine();
                String[] strings=str.split("#");//把字符串分割开，以#为分隔符分割
                //将第一行数据赋给chessSize和chessType
                chessSize=stringToInt(strings[0]);
                chessType=stringToInt(strings[1]);
                //读出chessIndexArr数组中的数据
                for (int i = 0; i < chessSize; i++) {
                    str= bufferedReader.readLine();
                    strings=str.split("#");
                    chessIndexArr[i][0]=stringToInt(strings[0]);
                    chessIndexArr[i][1]=stringToInt(strings[1]);
                }
                //读出chessArray数组中的数据
                for (int i = 0; i < ROW; i++) {
                    str= bufferedReader.readLine();
                    for (int j = 0; j < COL; j++) {
                        chessArray[i][j]=str.charAt(j)-'0';
                    }
                }

                bufferedReader.close();//关闭文件
                System.out.println("已读出"+file.getPath());
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
    public static void replay(Graphics g,int index){
        //取出对应棋子的行号、列号、类型
        row=chessIndexArr[index][0];
        col=chessIndexArr[index][1];
        chessType=chessArray[row][col];
        //画棋子
        drawChess(g,index+1);
    }
}
// 命令模式：开始命令
class StartCommand implements Command {
    @Override
    public void execute(Graphics g) {
        if (Utils.chessSize == 0) {
            System.out.println("开始");
            Utils.chessType = 3;
        }
    }
}

// 命令模式：悔棋命令
class UndoCommand implements Command {
    @Override
    public void execute(Graphics g) {
        Utils.reBackChess(g);
    }
}

// 命令模式：重新开始命令
class RestartCommand implements Command {
    @Override
    public void execute(Graphics g) {
        Utils.reGame(g);
    }
}

// 命令模式：保存命令
class SaveCommand implements Command {
    @Override
    public void execute(Graphics g) {
        Utils.saveGame();
    }
}

// 命令模式：继续命令
class ContinueCommand implements Command {
    @Override
    public void execute(Graphics g) {
        Utils.readGame();
        int tempType = Utils.chessType;
        Utils.drawChessPad(g);
        Utils.drawChessArray(g);
        Utils.chessType = tempType;
    }
}

// 命令模式：复盘命令
class ReplayCommand implements Command {
    @Override
    public void execute(Graphics g) {
        Utils.replayIndex = 0;
        Utils.readGame();
        Utils.drawChessPad(g);
    }
}
// 工厂模式：命令工厂
class CommandFactory {
    public static Command createCommand(String commandName) {
        switch (commandName) {
            case "开始":
                return new StartCommand();
            case "悔棋":
                return new UndoCommand();
            case "重新开始":
                return new RestartCommand();
            case "保存":
                return new SaveCommand();
            case "继续":
                return new ContinueCommand();
            case "复盘":
                return new ReplayCommand();
            default:
                return null;
        }
    }
}